package com.easylinkin.linkappapi.deviceservice.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.easylinkin.bases.redis.util.RedisUtil;
import com.easylinkin.linkappapi.annotation.CustomMethodTimer;
import com.easylinkin.linkappapi.common.exceptions.BusinessException;
import com.easylinkin.linkappapi.device.constant.DeviceConstant.ModelCategoryEnum;
import com.easylinkin.linkappapi.device.entity.Device;
import com.easylinkin.linkappapi.device.service.DeviceService;
import com.easylinkin.linkappapi.device.service.SprayRecordsService;
import com.easylinkin.linkappapi.device.util.DeviceUtil;
import com.easylinkin.linkappapi.deviceparm.entity.DeviceParm;
import com.easylinkin.linkappapi.deviceparm.service.DeviceParmService;
import com.easylinkin.linkappapi.deviceservice.entity.DeviceServices;
import com.easylinkin.linkappapi.deviceservice.mapper.DeviceServicesMapper;
import com.easylinkin.linkappapi.deviceservice.service.DeviceServiceService;
import com.easylinkin.linkappapi.lobar.entity.Gate;
import com.easylinkin.linkappapi.lobar.entity.UserGateLink;
import com.easylinkin.linkappapi.lobar.entity.UserProject;
import com.easylinkin.linkappapi.lobar.entity.emp.EmpUserBase;
import com.easylinkin.linkappapi.lobar.mapper.EmpUserBaseMapper;
import com.easylinkin.linkappapi.lobar.mapper.UserGateLinkMapper;
import com.easylinkin.linkappapi.lobar.mapper.UserProjectMapper;
import com.easylinkin.linkappapi.openapi.dto.ApiDownResultDTO;
import com.easylinkin.linkappapi.openapi.dto.DelayItem;
import com.easylinkin.linkappapi.openapi.service.OpenApiService;
import com.easylinkin.linkappapi.security.context.LinkappUserContextProducer;
import com.easylinkin.linkappapi.security.entity.LinkappUser;

import java.util.*;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.annotation.RegEx;
import javax.annotation.Resource;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author TongJie
 * @since 2020-04-15
 */
@Service
@Slf4j
public class DeviceServicesServiceImpl extends ServiceImpl<DeviceServicesMapper, DeviceServices> implements DeviceServiceService {

    /**
     * 日志对象
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(DeviceServicesServiceImpl.class);

    @Resource
    private DeviceParmService deviceParmService;
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private LinkappUserContextProducer linkappUserContextProducer;
    @Resource
    private OpenApiService openApiService;
    @Resource
    private DeviceService deviceService;
    @Resource
    private DeviceServiceService deviceServiceService;
    @Resource
    private SprayRecordsService sprayRecordsService;

    @Autowired
    private UserProjectMapper userProjectMapper;

    @Autowired
    private UserGateLinkMapper userGateLinkMapper;

    @Autowired
    private EmpUserBaseMapper empUserBaseMapper;

    @Value("${linkapp.airControllerDelay}")
    public Long airControllerDelay;

    @Value("${gateparam.firsttime}")
    public Integer firstTime;

    @Value("${gateparam.secondtime}")
    public Integer secondTime;

    @Value("${gateparam.thirdtime}")
    public Integer thirdTime;

    @Value("${gateparam.maxtimes}")
    public Integer maxTimes;

    /**
     * 延迟队列
     */
    private DelayQueue<DelayItem> delayQueue = new DelayQueue<>();
    static ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    static {
        threadPoolTaskExecutor.setCorePoolSize(50);
        threadPoolTaskExecutor.setMaxPoolSize(100);
        threadPoolTaskExecutor.setQueueCapacity(100);
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        threadPoolTaskExecutor.afterPropertiesSet();
    }


    @Override
    @CustomMethodTimer
    public List<DeviceServices> getDeviceServicesList(DeviceServices deviceServices) {
        Assert.notNull(deviceServices, "参数为空");
        log.info("慢调用接口{}, params={}", "getDeviceServicesList", JSON.toJSONString(deviceServices));
//        同步时 查询服务给不到租户id
        LinkappUser current = linkappUserContextProducer.getCurrent();
        if (current != null) {
            deviceServices.setTenantId(current.getTenantId());
        }
        List<DeviceServices> deviceServiceList = baseMapper.getDeviceServicesList(deviceServices);
        //  父子属性分层格式化
        copyFormatDeviceServices(deviceServiceList);
        return deviceServiceList;
    }

    /**
     * 将设备服务 父子属性分层格式化
     */
    public void copyFormatDeviceServices(List<DeviceServices> deviceServiceList) {
        if (ObjectUtils.isEmpty(deviceServiceList)) {
            return;
        }
        for (DeviceServices services : deviceServiceList) {
            List<DeviceParm> topParms = deviceParmService.copyFormatDeviceParmList(services.getParamList());
            services.setParamList(topParms);
        }
    }

    @Override
    public ApiDownResultDTO send(DeviceServices deviceServices) {
        Assert.notNull(deviceServices, "设备服务参数为空");
        sprayRecordsService.downSendSpray(deviceServices.getDownParameter());
        return send(deviceServices.getDownParameter());
    }

    @Override
    public ApiDownResultDTO batchDeviceSend(DeviceServices deviceServices, Boolean delay) {
        Assert.notNull(deviceServices.getDownParameter(), "设备服务参数为空");
        Map<String, Object> downParameter = deviceServices.getDownParameter();
        ApiDownResultDTO result = null;

        List<String> deviceIdList = (List<String>)downParameter.get("device_id");
        Map<String, Object> downParameterNew = new HashMap<String, Object>();
        Boolean flag = false;
        if(deviceIdList == null || deviceIdList.isEmpty()){
            result = new ApiDownResultDTO();
            result.setCode("1");
            result.setMsg("设备信息参数不能为空");
            return result;
        }else if(deviceIdList != null && deviceIdList.size() == 1){
            flag = true;
        }

        String msg = "";
        for (String deviceId:
                deviceIdList) {
            downParameterNew.put("device_id", deviceId);
            downParameterNew.put("parameter", downParameter.get("parameter"));
            downParameterNew.put("service_id", downParameter.get("service_id"));
            result = send(downParameterNew);
            if(!"0".equalsIgnoreCase(result.getCode()) || result.getRequestId() == null){
                msg += "设备编码:" + deviceId +"下发失败" + ", 响应码：" + result.getCode() + ", 响应信息：" + result.getMsg() + ";";
            }
            if(delay){
                try{
                    Thread.sleep(airControllerDelay);
                }catch(Exception ex){
                    ex.printStackTrace();
                }
            }
        }
        if(flag){
            return result;
        }else {

            result = new ApiDownResultDTO();
            if("".equals(msg)){
                result.setCode("0");
                result.setMsg("success");
            }else {
                result.setCode("1");
                result.setMsg(msg);
            }
        }
        return result;
    }

    @Override
    public ApiDownResultDTO send(Map<String, Object> downParameter) {
        Assert.notNull(downParameter, "下发数据为空");
        downParameter.put("clear", 0);
        downParameter.put("confirm", 1);
        downParameter.put("schedule", 1);
        ApiDownResultDTO result ;
        String requestId = null;
        try {
            LOGGER.info("/api/down 发送请求参数：{}", JSON.toJSONString(downParameter));
            result = openApiService.dataDown(downParameter);
            LOGGER.info("/api/down 发送结果：{}", result);
            if(result == null ){
                result = new ApiDownResultDTO();
                result.setCode("1");
                result.setMsg("调用linkthings未响应");
                return result;
            }
            result.setRequestMsg(downParameter);
//            "0" 代表发送给linkthing 成功了，但设备不一定成功产生动作了
//            如果没有成功 或没有requestId就直接结束方法，不需要重发
            if (!"0".equalsIgnoreCase(result.getCode()) || StringUtils.isEmpty(result.getRequestId())) {
                return result;
            }
//            取得到 "requestId"的情况只有 redis中存放了。第一次发送 obj是null
            Object obj = downParameter.get("requestId");
//            String requestId = obj != null ? obj.toString() : result.getRequestId();
//            如果参数里面有requestId 说明是由重发而触发的
            if (obj != null) {
//                如果下发后有requestId则用新的覆盖旧有的，因为每次发送命令，requestId都是新生成的
                requestId = StringUtils.isNotEmpty(result.getRequestId()) ? result.getRequestId() : obj.toString();

            } else {
//                如果参数里面没有requestId，说明是第一次触发下行
                requestId = result.getRequestId();
            }

            Assert.notNull(downParameter.get("device_id"),"设备code为空");

            QueryWrapper<com.easylinkin.linkappapi.device.entity.Device> qw = new QueryWrapper<>();
            qw.select("device_unit_id").eq("code", downParameter.get("device_id")).eq("delete_state", 1);
            List<com.easylinkin.linkappapi.device.entity.Device> devices = deviceService.list(qw);
            Assert.notEmpty(devices, "下发查询到设备为空,查询条件-code:" + downParameter.get("device_id"));
            com.easylinkin.linkappapi.device.entity.Device device = devices.get(0);
            QueryWrapper<DeviceServices> qw2 = new QueryWrapper<>();
            qw2.eq("device_unit_id", device.getDeviceUnitId()).eq("identifier", downParameter.get("service_id"));
            List<DeviceServices> deviceServicesList = deviceServiceService.list(qw2);
            Assert.notEmpty(deviceServicesList,"下发查询到设备服务为空,查询条件-identifier:" + downParameter.get("service_id") + ",device_unit_id:" + device.getDeviceUnitId());
            DeviceServices deviceServices = deviceServicesList.get(0);
//            如果设备服务是异步应答类型的 或者 设备服务应答类型是空的，都需要做重发机制.这么做的效果就是只把同步的和无应答的不需要做重发
            if (deviceServices.getAckType() == null || deviceServices.getAckType().equals(1)) {
//                默认是10秒
                Integer expiredSeconds = 10;
                if (deviceServices.getAckTimeOut() != null) {
//                默认重发等待时间 10秒
                    expiredSeconds = deviceServices.getAckTimeOut();
                }
//								每expiredSeconds秒重发一次 ,重发3次
//								此处会触发 com.easylinkin.linkappapi.redis.RedisKeyExpireListener.onApplicationEvent 的事件监听方法
//								发完后，如果设备成功响应不需要重发了，就会推一条流水，就会去删掉_down 和_down_param,从而就不会触发redis键值失效事件
//								删除_down逻辑在方法com.easylinkin.linkappapi.openapi.service.impl.DataPushServiceImpl.dataPush2中

//                替换为延迟队列的实现方案  by 2021/08/31  tongjie
//                redisUtil.set(requestId + "_down", "", expiredSeconds);

//                向延迟队列放入消息
                String itemName = requestId + "_down";
                DelayItem delayItem = new DelayItem(itemName, expiredSeconds, TimeUnit.SECONDS);
                delayQueue.put(delayItem);

                downParameter.put("requestId", requestId);
//            _down 失效事件触发，要取的参数从 另一里面取，失效时间更长5倍的时间失效
                redisUtil.set(itemName + "_param", downParameter, 5L * expiredSeconds);
                LOGGER.info("重发设置_down_param成功,key:{},downParameter：{}", requestId, downParameter);
            }
        } catch (Exception e) {
            LOGGER.error("设置重发key失败,requestId是：{}，失败原因：{}", requestId, e);
            throw new BusinessException("发送失败", e.getMessage());
        }
        LOGGER.info("/api/down 发送结果2：{}", JSON.toJSONString(result));
        return result;
    }

    @Override
    public ApiDownResultDTO sendToGate(Map<String, Object> downParameter) {
        Assert.notNull(downParameter, "下发数据为空");
        downParameter.put("clear", 0);
        downParameter.put("confirm", 1);
        downParameter.put("schedule", 1);
        ApiDownResultDTO result ;
        String requestId = null;
        try {
            LOGGER.info("/api/down 发送请求参数：{}", JSON.toJSONString(downParameter));
            result = openApiService.dataDown(downParameter);
            LOGGER.info("/api/down 发送结果：{}", result);
            if(result == null ){
                result = new ApiDownResultDTO();
                result.setCode("1");
                result.setMsg("调用linkthings未响应");
                return result;
            }
            if (result.getMsg().contains("设备异常")) {
                return result;
            }
            result.setRequestMsg(downParameter);
//            取得到 "requestId"的情况只有 redis中存放了。第一次发送 obj是null
            Object obj = downParameter.get("requestId");
//            如果参数里面有requestId 说明是由重发而触发的
            if (obj != null) {
//                如果下发后有requestId则用新的覆盖旧有的，因为每次发送命令，requestId都是新生成的
                requestId = StringUtils.isNotEmpty(result.getRequestId()) ? result.getRequestId() : obj.toString();

            } else {
//                如果参数里面没有requestId，说明是第一次触发下行
                requestId = result.getRequestId();
            }
//            如果请求失败，则需要重发
            if (!"0".equals(result.getCode())) {
//                默认是10秒
                Integer expiredSeconds = firstTime;
//                设置放入延迟队列标志
                boolean putFlag = true;
                if (downParameter.get("count") != null) {
                    Integer count = (Integer) downParameter.get("count");
//                默认重发等待时间 10秒
                    if (count < 3) {
                        expiredSeconds = (count + 1) * secondTime;
                    } else {
                        expiredSeconds = thirdTime;
                    }
                    putFlag = count < maxTimes ? true : false;
                }

                if (putFlag) {
//                向延迟队列放入消息
                    String itemName = null;
                    if (requestId == null) {
                        itemName = System.currentTimeMillis() + "_newDown";
                    } else {
                        itemName = requestId + "_newDown";
                    }
                    DelayItem delayItem = new DelayItem(itemName, expiredSeconds, TimeUnit.SECONDS);
                    delayQueue.put(delayItem);

                    downParameter.put("requestId", requestId);
//            _down 失效事件触发，要取的参数从 另一里面取，失效时间更长5倍的时间失效
                    redisUtil.set(itemName + "_param", downParameter, 5L * expiredSeconds);
                    LOGGER.info("重发设置_down_param成功,key:{},downParameter：{}", requestId, downParameter);
                }
            }
        } catch (Exception e) {
            LOGGER.error("设置重发key失败,requestId是：{}，失败原因：{}", requestId, e);
            throw new BusinessException("发送失败", e.getMessage());
        }
        LOGGER.info("/api/down 发送结果2：{}", JSON.toJSONString(result));
        return result;
    }

    @Override
    public String downSendLogMessage(Map<String, Object> downParameter) {
        String message = "";
        String deviceCode = (String) downParameter.get("device_id");
        // 校验是否是喷淋设备
        Device device = new Device();
        device.setCode(deviceCode);
        List<Device> deviceList = deviceService.selectDevices(device);
        if (deviceList == null || deviceList.size() <= 0) {
            return message;
        }
        device = deviceList.get(0);
        if(DeviceUtil.isBelongSpecifiedDeviceType(device,ModelCategoryEnum.SPARY.getDeviceTypeNameKey())){
            Map<String,Object> map = (Map<String, Object>) downParameter.get("parameter");
            if(null == map.get("switch_state")){
                return message;
            }
            Integer switchState = Integer.parseInt(String.valueOf(map.get("switch_state")));
            if(null == switchState){
                return message;
            }
            message = (1 == switchState ? "开启喷淋，设备名称:" : "关闭喷淋，设备名称:") + device.getName();
        }else{// 补充其他设备类型日志
            message = "设备名称:" + device.getName();
        }

        return message;
    }

    @PostConstruct
    public void handleResendMessage() {
        log.info("handleResendMessage--init--");
        threadPoolTaskExecutor.execute(this::resendByQueue);
    }

    private void resendByQueue() {
        while (true) {
            try {
                DelayItem delayItem = delayQueue.take();
                // take方法，有消息（延时时间<0的消息）取消息，没有，阻塞住。
                String delayItemName = delayItem.getName();
                if (delayItemName != null && delayItemName.endsWith("_down")) {
                    log.info("收到延迟队列down的消息:{}", delayItemName);
                    if (redisUtil.hasKey(delayItemName + "_param")) {
                        resend(delayItem.getName());
                    }
                }
                if (delayItemName != null && delayItemName.endsWith("_newDown")) {
                    log.info("收到延迟队列down的消息:{}", delayItemName);
                    if (redisUtil.hasKey(delayItemName + "_param")) {
                        newResend(delayItemName);
                    }
                }
            } catch (Exception e) {
                log.error("收到延迟队列的消息发生异常：", e);
            }
        }
    }


    private void resend(String key) {
//      解决本地启动了网络延迟不一致，致使的多次重发,若已重发了标记为已使用
//        String usedKey = key + "_resend_used";
//        if (redisUtil.hasKey(usedKey)) {
//            return;
//        }
//        log.info("key到期触发，得到了重发的初步允许：" + key);
//        redisUtil.set(usedKey, true, 5);
//		    		_lock是为了解决多实例问题 同步锁
        log.info("resend重发被调用：" + key);
        Object obj = redisUtil.get(key + "_param");
        redisUtil.del(key + "_param");
        if (obj == null) {
            log.info("send down param不见了：" + key);
            return;
        }
        Map<String, Object> map = (Map<String, Object>) obj;
        if (map.get("count") != null) {
            Integer count = (Integer) map.get("count");
            if (count >= 3) {
                log.info("resend次数达到：{}，key:{}，结束返回", count, key);
                return;
            }
            count++;
            map.put("count", count);
        } else {
            map.put("count", 1);
        }
        log.info("重发，第{}次：{}", map.get("count"), map);
        deviceServiceService.send(map);
    }

    private void newResend(String key) {
        log.info("resend重发被调用：" + key);
        Object obj = redisUtil.get(key + "_param");
        redisUtil.del(key + "_param");
        if (obj == null) {
            log.info("send down param不见了：" + key);
            return;
        }
        Map<String, Object> map = (Map<String, Object>) obj;
        if (map.get("count") != null) {
            Integer count = (Integer) map.get("count");
            map.put("count", ++count);
        } else {
            map.put("count", 1);
        }
        log.info("重发，第{}次：{}", map.get("count"), map);

        // 用户闸机表数据补充数据
        String userGateLinkId = null;
        boolean delFlag = false;
        if (map.get("insertFlag") != null && "true".equals(map.get("insertFlag").toString())) {
            //查询当前系统中用户的在岗状态。若在岗，则可以继续走流程；反之，则终止流程
            QueryWrapper<UserProject> wrapper = new QueryWrapper<>();
            wrapper.eq("tenant_id_", map.get("tenantId").toString()).eq("user_id_", map.get("userId").toString());
            UserProject userProject = userProjectMapper.selectOne(wrapper);
            if ("0".equals(String.valueOf(userProject.getStatus()))) {
                log.info("用户已离场，无法继续流程");
                return;
            }

            //查询绑定关系表中是否已存在绑定关系
            QueryWrapper<UserGateLink> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("user_id_", map.get("userId").toString())
                    .eq("tenant_id_", map.get("tenantId").toString()).eq("gate_id_", map.get("gateId").toString());
            List<UserGateLink> gateLinks = userGateLinkMapper.selectList(queryWrapper);
            UserGateLink gateLink = gateLinks.size() > 0 ? gateLinks.get(0) : null;
            String idPre = Gate.getIdPre();
            if (gateLink == null) {
                delFlag = true;
                UserGateLink userGateLink = new UserGateLink();
                userGateLink.setModifyTime(new Date());
                userGateLink.setCreateTime(new Date());
                userGateLink.setTenantId(map.get("tenantId").toString());
                userGateLink.setGateId(map.get("gateId").toString());
                userGateLink.setUserId(map.get("userId").toString());
                userGateLinkMapper.insert(userGateLink);
                userGateLinkId = String.valueOf(userGateLink.getId());
                Map<String, Object> parameter = (Map<String, Object>) map.get("parameter");
                parameter.put("id", idPre + userGateLinkId);
                map.put("parameter", parameter);
            } else {
                userGateLinkId = String.valueOf(gateLink.getId());
                EmpUserBase empUserBase = empUserBaseMapper.selectById(map.get("userId").toString());
                Map<String, Object> parameter = (Map<String, Object>) map.get("parameter");
                parameter.put("id", idPre + userGateLinkId);
                parameter.put("idCard", empUserBase.getCard());
                parameter.put("name", empUserBase.getName());
                parameter.put("regImage", empUserBase.getPhoto());
                map.put("parameter", parameter);
            }
        } else if ("delPerson".equals(map.get("service_id").toString()) && map.get("userGateLinkId") != null) {
            // 校验是否已退场
            UserGateLink gateLink = userGateLinkMapper.selectById(map.get("userGateLinkId").toString());
            if (gateLink == null) {
                log.info("该人员无需退场");
                return;
            }
        }
        ApiDownResultDTO result = deviceServiceService.sendToGate(map);

        //如果重发成功，则修复相关数据
        if ("0".equals(result.getCode())) {
            // 下发闸机-增加人员
            if ("uploadPerson".equals(map.get("service_id").toString())) {
                // 更新用户项目表app_user_project 下发闸机状态 gate_status_
                UpdateWrapper<UserProject> updateWrapper = new UpdateWrapper<>();
                updateWrapper.set("gate_status_", 1)
                        .eq("tenant_id_", map.get("tenantId").toString())
                        .eq("user_id_", map.get("userId").toString());
                userProjectMapper.update(null, updateWrapper);
            }

            // 下发闸机-删除人员
            if ("delPerson".equals(map.get("service_id").toString()) && map.get("userGateLinkId") != null) {
                // 删除关联关系
                userGateLinkMapper.deleteById(map.get("userGateLinkId").toString());
            }
        } else {
            // 失败了，如果之前做了插入操作，则需要删除关联关系
            if (map.get("insertFlag") != null && "true".equals(map.get("insertFlag").toString()) && delFlag) {
                userGateLinkMapper.deleteById(userGateLinkId);
            }
        }
    }



}
